package org.eerbaar.lang.runtime;

import java.lang.reflect.Type;

import org.eerbaar.lang.Expression;
import org.eerbaar.lang.Values;
import org.eerbaar.lang.expressions.IntVar;
import org.eerbaar.lang.expressions.StringVar;
import org.eerbaar.lang.expressions.Equal;
import org.eerbaar.lang.expressions.If;
import org.eerbaar.lang.expressions.Loop;
import org.eerbaar.lang.expressions.Print;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class JSON {

    public JSON() {
        //
    }

    private static class ForInstanceCreator implements InstanceCreator<Loop> {
        public Loop createInstance(Type type) {
            return new Loop();
        }
    }

    private static class ExpressionDeserializer implements
            JsonDeserializer<Expression> {
        public Expression deserialize(JsonElement json, Type typeOfT,
                JsonDeserializationContext context) throws JsonParseException {

            Expression result = null;

            if (json.isJsonPrimitive()) {
                if (json.getAsString().equals("UNDEF")) {
                    result = Values.UNDEF;
                } else if (json.getAsString().equals("TRUE")) {
                    result = Values.TRUE;
                } else if (json.getAsString().equals("FALSE")) {
                    result = Values.FALSE;
                }
            } else {
                JsonObject obj = json.getAsJsonObject();
                String type = obj.get("type").getAsString();
                if (type.equals("print")) {
                    result = context.deserialize(json, Print.class);
                } else if (type.equals("loop")) {
                    result = context.deserialize(json, Loop.class);
                } else if (type.equals("if")) {
                    result = context.deserialize(json, If.class);
                } else if (type.equals("equal")) {
                    result = context.deserialize(json, Equal.class);
                } else if (type.equals("string")) {
                    result = context.deserialize(json, StringVar.class);
                } else if (type.equals("integer")) {
                    result = context.deserialize(json, IntVar.class);
                }
            }

            return result;
        }
    }

    private static class ExpressionSerializer implements
            JsonSerializer<Expression> {

        public JsonElement serialize(Expression src, Type typeOfSrc,
                JsonSerializationContext context) {

            JsonElement e = context.serialize(src, src.getClass());

            String s = null;
            if (src instanceof Print) {
                s = "print";
            } else if (src instanceof Loop) {
                s = "loop";
            } else if (src instanceof If) {
                s = "if";
            } else if (src instanceof Equal) {
                s = "equal";
            } else if (src instanceof StringVar) {
                s = "string";
            } else if (src instanceof IntVar) {
                s = "integer";
            }
            if (s != null) {
                e.getAsJsonObject().addProperty("type", s);
            }

            return e;
        }

    }

    public static Gson createGson() {

        GsonBuilder builder = new GsonBuilder();

        builder.registerTypeAdapter(Loop.class, new ForInstanceCreator());
        builder.registerTypeAdapter(Expression.class,
                new ExpressionDeserializer());
        builder.registerTypeAdapter(Expression.class,
                new ExpressionSerializer());

        builder.setPrettyPrinting();

        return builder.create();
    }

}
